#include "common.h"
#include <stdlib.h>
#include <dlfcn.h>

#ifndef REF_SO
# error Please define REF_SO to the path of NEMU shared object file
#endif

void (*ref_difftest_memcpy_from_dut)(paddr_t dest, void *src, size_t n) = NULL;
void (*ref_difftest_getregs)(void *c) = NULL;
void (*ref_difftest_setregs)(const void *c) = NULL;
void (*ref_difftest_exec)(uint64_t n) = NULL;
void (*ref_difftest_raise_intr)(uint64_t NO) = NULL;
void (*ref_isa_reg_display)(void) = NULL;

void dut_reg_display(rtlreg_t *reg);

void init_difftest(rtlreg_t *reg) {
  void *handle;
  handle = dlopen(REF_SO, RTLD_LAZY | RTLD_DEEPBIND);
   assert(handle);

  ref_difftest_memcpy_from_dut = (void (*)(paddr_t, void *, size_t))dlsym(handle, "difftest_memcpy_from_dut");
  assert(ref_difftest_memcpy_from_dut);

  ref_difftest_getregs = (void (*)(void *))dlsym(handle, "difftest_getregs");
  assert(ref_difftest_getregs);

  ref_difftest_setregs = (void (*)(const void *))dlsym(handle, "difftest_setregs");
  assert(ref_difftest_setregs);

  ref_difftest_exec = (void (*)(uint64_t))dlsym(handle, "difftest_exec");
  assert(ref_difftest_exec);

  ref_difftest_raise_intr = (void (*)(uint64_t))dlsym(handle, "difftest_raise_intr");
  assert(ref_difftest_raise_intr);

  ref_isa_reg_display = (void (*)(void))dlsym(handle, "isa_reg_display");
  assert(ref_isa_reg_display);

  void (*ref_difftest_init)(void) = (void (*)(void))dlsym(handle, "difftest_init");
  assert(ref_difftest_init);


  ref_difftest_init();
  void* get_img_start();
        
  long get_img_size();
  ref_difftest_memcpy_from_dut(0x80000000, get_img_start(), get_img_size());
  ref_difftest_setregs(reg);
}

static const char *reg_name[DIFFTEST_NR_REG] = {
  "$0", "ra", "sp", "gp", "tp", "t0", "t1", "t2",
  "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5",
  "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7",
  "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6",
  "this_pc"
#ifndef __RV32__
  ,"mstatus", "mcause", "mepc",
  "sstatus", "scause", "sepc"
#endif
};

int difftest_step(rtlreg_t *reg_scala, uint32_t this_inst,
    int isMMIO, int isRVC, uint64_t intrNO) {

  //#define DEBUG_RETIRE_TRACE_SIZE 16


  rtlreg_t ref_r[DIFFTEST_NR_REG];
  rtlreg_t this_pc = reg_scala[DIFFTEST_THIS_PC];
  

  static rtlreg_t nemu_this_pc = 0x80000000;
  static int need_copy_pc = 0;

 
  if (need_copy_pc) {
    need_copy_pc = 0;
    ref_difftest_getregs(&ref_r); // get rf info saved in former cycle
    nemu_this_pc = reg_scala[DIFFTEST_THIS_PC]; // we assume target's pc is right
    ref_r[DIFFTEST_THIS_PC] = reg_scala[DIFFTEST_THIS_PC];
    ref_difftest_setregs(ref_r);
  }
  if (isMMIO) {                
    int pc_skip = 0;
    pc_skip += isRVC ? 2 : 4;
    // pc_skip += isMultiCommit ? (isRVC2 ? 2 : 4) : 0;
    reg_scala[DIFFTEST_THIS_PC] += pc_skip;
    nemu_this_pc += pc_skip;
    ref_difftest_setregs(reg_scala);
    // pc_retire_pointer = (pc_retire_pointer+1) % DEBUG_RETIRE_TRACE_SIZE;
    // pc_retire_queue[pc_retire_pointer] = this_pc;
    // inst_retire_queue[pc_retire_pointer] = this_inst;
    // multi_commit_queue[pc_retire_pointer] = isMultiCommit;
    // skip_queue[pc_retire_pointer] = isMMIO;
    need_copy_pc = 1;
    return 0;                                                                                                                        // NEMU并不执行，直接return
  }

  if (intrNO) {
    ref_difftest_raise_intr(intrNO);
  } else {
    ref_difftest_exec(1);
  }
//  ref_difftest_exec(1);

  ref_difftest_getregs(&ref_r);
 
  rtlreg_t next_pc = ref_r[32];

  // replace with "this pc" for checking
  ref_r[DIFFTEST_THIS_PC] = nemu_this_pc;
  nemu_this_pc = next_pc;

  if(memcmp(reg_scala, ref_r, 8 * (DIFFTEST_MEPC + 1)) != 0){
      printf(FONT_COLOR_RED"\t============ DIFFTEST FAIL!!! ============\t\n"COLOR_NONE);
      printf("difftest abort at pc=%08lx\n", this_pc);
      
      ref_isa_reg_display();
      //dut_reg_display(reg_scala);
      for (int i = 0; i < DIFFTEST_MEPC + 1; i++) {
          if (reg_scala[i] != ref_r[i]) {
              printf(FONT_COLOR_RED"%s different at pc = 0x%010lx, right= 0x%016lx, wrong = 0x%016lx\n"COLOR_NONE,
                  reg_name[i], this_pc, ref_r[i], reg_scala[i]);
          }
      }
      return 1;
  }
  return 0;
}

void dut_reg_display(rtlreg_t *reg)
{
    printf("reg scala:\n");
    for (int i = 0; i < DIFFTEST_GPR_NUM; i ++){
        printf("%s: 0x%016lx ", reg_name[i], reg[i]);
        if(i % 4 == 3)
            printf("\n");
    }
}